
前面的章节中，我们在LLVM中开发了M88k目标的后端实现。为了完成M88k目标的编译器实现，通过为M88k目标添加clang实现来研究将新目标连接到前端。

\mySubsubsection{13.2.1.}{clang中实现驱动程序的集成}

让我们从添加驱动程序集成到M88k的clang开始:

\begin{enumerate}
\item
要做的第一个更改是在clang/include/clang/Basic/TargetInfo.h文件中。BuiltinVaListKind枚举列出了每个目标的不同类型的\_\_ builtin\_va\_list类型，用于支持可变函数，因此为M88k添加了相应的类型:

\begin{cpp}
enum BuiltinVaListKind {
    . . .
    // typedef struct __va_list_tag {
        // int __va_arg;
        // int *__va_stk;
        // int *__va_reg;
        //} va_list;
    M88kBuiltinVaList
};
\end{cpp}

\item
接下来，必须添加一个新的头文件clang/lib/Basic/Targets/M88k.h。该文件是前端中M88k目标特性支持的头文件。第一步是定义一个新的宏，以防止多个包含相同的头文件、类型、变量等，还必须包括实现所需的各种头文件:

\begin{cpp}
#ifndef LLVM_CLANG_LIB_BASIC_TARGETS_M88K_H
#define LLVM_CLANG_LIB_BASIC_TARGETS_M88K_H
#include "OSTargets.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Basic/TargetOptions.h"
#include "llvm/Support/Compiler.h"
#include "llvm/TargetParser/Triple.h"
\end{cpp}

\item
将声明的方法将相应地添加到clang和targets命名空间中，就像llvm-project中的其他目标一样:

\begin{cpp}
namespace clang {
namespace targets {
\end{cpp}

\item
现在，声明实际的M88kTargetInfo类，并让它扩展原来的TargetInfo类。若这个类链接到动态库，这个类标记为LLVM\_LIBRARY\_VISIBILITY，这个属性允许M88kTargetInfo类只在库中可见，而在外部不可访问:

\begin{cpp}
class LLVM_LIBRARY_VISIBILITY M88kTargetInfo: public TargetInfo
{
\end{cpp}

\item
另外，必须声明两个变量——一个表示寄存器名的字符数组和一个枚举值，其中包含M88k目标中可选择的可用CPU类型。我们设置的默认CPU是CK\_Unknown CPU。稍后，将看到这可以用户如何通过选项对此进行修改:

\begin{cpp}
    static const char *const GCCRegNames[];
    enum CPUKind { CK_Unknown, CK_88000, CK_88100, CK_88110 } CPU = CK_Unknown;
\end{cpp}

\item
之后，首先声明类实现中需要的公共方法。除了类的构造函数之外，还定义了各种getter方法。这包括获得目标特定的\#define值的方法，获得目标支持的内置列表的方法，返回GCC寄存器名及其别名的方法，最后，一个返回之前添加到clang/include/clang/Basic/TargetInfo.h的M88k BuiltinVaListKind方法。

\begin{cpp}
public:
    M88kTargetInfo(const llvm::Triple &Triple, const TargetOptions&);
    void getTargetDefines(const LangOptions &Opts,
    MacroBuilder &Builder) const override;
    ArrayRef<Builtin::Info> getTargetBuiltins() const override;
    ArrayRef<const char *> getGCCRegNames() const override;
    ArrayRef<TargetInfo::GCCRegAlias> getGCCRegAliases() const override;
    BuiltinVaListKind getBuiltinVaListKind() const override {
        return TargetInfo::M88kBuiltinVaList;
    }
\end{cpp}

\item
getter方法之后，还必须定义对M88k目标执行各种检查的方法。第一个检查M88k目标是否具有特定的目标特性，以字符串的形式提供。当使用内联汇编时，添加一个函数来验证约束。最后，们有一个函数来检查一个特定的CPU是否对M88k目标有效，也以字符串的形式提供:

\begin{cpp}
    bool hasFeature(StringRef Feature) const override;
    bool validateAsmConstraint(const char *&Name,
                                TargetInfo::ConstraintInfo &info)
                                const override;
    bool isValidCPUName(StringRef Name) const override;
\end{cpp}

\item
接下来，为M88kTargetInfo类声明setter方法。第一个方法简单地设置了我们想要瞄准的特定M88k CPU，而第二个方法设置了一个vector来包含M88k的所有有效支持的CPU:

\begin{cpp}
    bool setCPU(const std::string &Name) override;
    void fillValidCPUList(SmallVectorImpl<StringRef> &Values) const override;
};
\end{cpp}

\item
为了完成驱动程序实现的头文件，结束开头添加的名称空间和宏定义:

\begin{cpp}
} // namespace targets
} // namespace clang
#endif // LLVM_CLANG_LIB_BASIC_TARGETS_M88K_H
\end{cpp}

\item
现在，已经在clang/lib/Basic/Targets中完成了M88k头文件，必须在clang/lib/Basic/Targets/M88k.cpp中添加相应的TargetInfo C++实现。我们将从包含所需的头文件开始，特别是刚刚创建的新M88k.h头文件:

\begin{cpp}
#include "M88k.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/TargetBuiltins.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/TargetParser/TargetParser.h"
#include <cstring>
\end{cpp}

\item
正如之前在头文件中所做的那样，从clang和目标命名空间开始，然后实现M88kTargetInfo类的构造函数:

\begin{cpp}
namespace clang {
namespace targets {
M88kTargetInfo::M88kTargetInfo(const llvm::Triple &Triple,
                               const TargetOptions &)
    : TargetInfo(Triple) {
\end{cpp}

\item
构造函数中，为M88k目标设置了数据布局字符串。这个数据布局字符串位于发出的LLVM IR文件的顶部，数据布局字符串的每个部分的解释如下:

\begin{cpp}
    std::string Layout = "";
    Layout += "E"; // M68k is Big Endian
    Layout += "-m:e";
    Layout += "-p:32:32:32"; // Pointers are 32 bit.
    // All scalar types are naturally aligned.
    Layout += "-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64";

    // Floats and doubles are also naturally aligned.
    Layout += "-f32:32:32-f64:64:64";
    // We prefer 16 bits of aligned for all globals; see above.
    Layout += "-a:8:16";

    Layout += "-n32"; // Integer registers are 32bits.
    resetDataLayout(Layout);
\end{cpp}

\item
M88kTargetInfo类的构造函数通过将各种变量类型设置为signed long long、unsigned long或signed int来结束:

\begin{cpp}
    IntMaxType = SignedLongLong;
    Int64Type = SignedLongLong;
    SizeType = UnsignedLong;
    PtrDiffType = SignedInt;
    IntPtrType = SignedInt;
}
\end{cpp}

\item
完成目标器的CPU设置功能。这个函数接受一个字符串，并将CPU设置为用户在llvm::StringSwitch中提供的特定CPU字符串，其本质上只是一个常规的开关，但专用于使用llvm的字符串。可以看到M88k目标上有三种支持的CPU类型，提供的字符串不匹配任何预期的类型，则有CK\_Unknown类型:

\begin{cpp}
bool M88kTargetInfo::setCPU(const std::string &Name) {
    StringRef N = Name;
    CPU = llvm::StringSwitch<CPUKind>(N)
                .Case("generic", CK_88000)
                .Case("mc88000", CK_88000)
                .Case("mc88100", CK_88100)
                .Case("mc88110", CK_88110)
                .Default(CK_Unknown);
    return CPU != CK_Unknown;
}
\end{cpp}

\item
前面说过，在M88k目标上有三种支持的有效CPU类型:mc88000、mc88100和mc88110，其中通用类型只是mc88000 CPU。必须实现以下函数，在clang中强制执行这些有效的CPU。首先，必须声明一个字符串数组ValidCPUNames[]，以表示M88k上的有效CPU名称。其次，fillValidCPUList()方法将有效CPU名称数组填充到一个vector中。这个vector会在isValidCPUName()中使用，以检查提供的特定CPU名称是否确实对我们的M88k目标有效:

\begin{cpp}
static constexpr llvm::StringLiteral ValidCPUNames[] = {
    {"generic"}, {"mc88000"}, {"mc88100"}, {"mc88110"}};

void M88kTargetInfo::fillValidCPUList(
        SmallVectorImpl<StringRef> &Values) const {
    Values.append(std::begin(ValidCPUNames),
    std::end(ValidCPUNames));
}
bool M88kTargetInfo::isValidCPUName(StringRef Name) const {
    return llvm::is_contained(ValidCPUNames, Name);
}
\end{cpp}

\item
接下来，实现gettargetdefinitions()方法。这个函数定义了前端所必需的宏，比如有效的CPU类型。除了\_\_m88k\_\_和\_\_m88k宏，还必须为有效的CPU定义相应的CPU宏:

\begin{cpp}
void M88kTargetInfo::getTargetDefines(const LangOptions &Opts,
                                      MacroBuilder &Builder)
const {
    using llvm::Twine;
    Builder.defineMacro("__m88k__");
    Builder.defineMacro("__m88k");
    switch (CPU) { // For sub-architecture
    case CK_88000:
        Builder.defineMacro("__mc88000__");
        break;
    case CK_88100:
    Builder.defineMacro("__mc88100__");
    break;
    case CK_88110:
        Builder.defineMacro("__mc88110__");
        break;
    default:
        break;
    }
}
\end{cpp}

\item
接下来的几个函数是存根函数，但对于前端的基本支持是必需的。这包括从目标获取内置函数的函数，以及在支持目标的特定特性时查询目标的函数。现在，保留它们，并为这些函数设置默认返回值，以便稍后实现:

\begin{cpp}
ArrayRef<Builtin::Info> M88kTargetInfo::getTargetBuiltins()
const {
    return std::nullopt;
}
bool M88kTargetInfo::hasFeature(StringRef Feature) const {
    return Feature == "M88000";
}
\end{cpp}

\item
这些函数之后，将在M88k上添加寄存器名的实现。通常，支持的寄存器名列表及其用途可以在感兴趣的特定平台的ABI上找到。这个实现中，将实现从0到31的主要通用寄存器，并创建一个数组来存储这些信息。寄存器别名方面，目前实现的寄存器没有别名:

\begin{cpp}
const char *const M88kTargetInfo::GCCRegNames[] = {
    "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7",
    "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15",
    "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23",
    "r24", "r25", "r26", "r27", "r28", "r29", "r39", "r31"};

ArrayRef<const char *> M88kTargetInfo::getGCCRegNames() const {
    return llvm::makeArrayRef(GCCRegNames);
}
ArrayRef<TargetInfo::GCCRegAlias>
M88kTargetInfo::getGCCRegAliases() const {
    return std::nullopt; // No aliases.
}
\end{cpp}

\item
我们要实现的最后一个函数是，验证目标上的内联汇编约束的函数。此函数仅接受一个字符(表示内联程序集约束)，并相应地处理该约束。实现了一些内联汇编寄存器约束，例如：地址寄存器、数据寄存器和浮点寄存器，并且还考虑了一些常量:

\begin{cpp}
bool M88kTargetInfo::validateAsmConstraint(
const char *&Name, TargetInfo::ConstraintInfo &info) const {
    switch (*Name) {
    case 'a': // address register
    case 'd': // data register
    case 'f': // floating point register
        info.setAllowsRegister();
        return true;
    case 'K': // the constant 1
    case 'L': // constant -1^20 .. 1^19
    case 'M': // constant 1-4:
        return true;
    }
    return false;
}
\end{cpp}

\item
我们通过关闭在文件开头启动的clang和targets命名空间来结束该文件:

\begin{cpp}
} // namespace targets
} // namespace clang
\end{cpp}

\end{enumerate}

完成clang/lib/Basic/Targets/M88k.cpp的实现后，必需在clang/include/clang/Driver/Options.td中添加M88k特性组和有效CPU类型的实现。

回想一下，之前为M88k目标定义了三种有效的CPU类型:mc88000、mc88100和mc88110。这些CPU类型也需要在Options中定义。因为这个文件是中心位置，定义了clang将接受的所有选项和标志:

\mySubsubsection{13.2.2.}{clang中实现对M88k的ABI支持}

现在，需要在clang的前端中添加ABI支持，可以从前端生成特定于M88k目标的代码:

\begin{enumerate}
\item
从添加以下clang/lib/CodeGen/TargetInfo.h开始，这是一个为M88k目标创建代码生成信息的原型:

\begin{cpp}
std::unique_ptr<TargetCodeGenInfo>
createM88kTargetCodeGenInfo(CodeGenModule &CGM);
\end{cpp}

\item
还需要将以下代码添加到clang/lib/Basic/Targets.cpp中，这将帮助clang了解M88k可接受的目标三元组。对于M88k目标，可接受的操作系统是OpenBSD，所以clang接受m88k-openbsd作为目标三元组:

\begin{cpp}
#include "Targets/M88k.h"
#include "Targets/MSP430.h"
...
    case llvm::Triple::m88k:
        switch (os) {
            case llvm::Triple::OpenBSD:
            return std::make_unique<OpenBSDTargetInfo<M88kTargetInfo>>(Triple, Opts);
        default:
            return std::make_unique<M88kTargetInfo>(Triple, Opts);
        }
    case llvm::Triple::le32:
...
\end{cpp}

现在，需要创建一个名为clang/lib/CodeGen/Targets/M88k.cpp的文件，以便可以继续M88k的代码生成信息和ABI实现。

\item
clang/lib/CodeGen/Targets/M88k.cpp中，必须添加以下必要的头文件，其中之一就是刚刚修改的TargetInfo.h头文件，必须指定使用clang和clang::codegen命名空间:

\begin{cpp}
#include "ABIInfoImpl.h"
#include "TargetInfo.h"
using namespace clang;
using namespace clang::CodeGen;
\end{cpp}

\item
接下来，必须声明一个新的匿名空间，并将M88kABIInfo放入其中。M88kABIInfo继承自clang中现有的ABIInfo，并在其中包含DefaultABIInfo。对于我们的目标，很大程度上依赖于现有的ABIInfo和DefaultABIInfo，这大大简化了M88kABIInfo类:

\begin{cpp}
namespace {
class M88kABIInfo final : public ABIInfo {
    DefaultABIInfo defaultInfo;
\end{cpp}

\item
此外，除了为M88kABIInfo类添加构造函数外，还添加了两个方法。computeInfo()实现默认的clang::CodeGen::ABIInfo类。还有EmitVAArg()函数，生成代码，从传入的指针中检索参数(这是更新后的)。这主要用于可变函数支持:

\begin{cpp}
public:
    explicit M88kABIInfo(CodeGen::CodeGenTypes &CGT)
        : ABIInfo(CGT), defaultInfo(CGT) {}
    void computeInfo(CodeGen::CGFunctionInfo &FI) const override
    {}
    CodeGen::Address EmitVAArg(CodeGen::CodeGenFunction &CGF,
                               CodeGen::Address VAListAddr,
                               QualType Ty) const override {
        return VAListAddr;
    }
};
\end{cpp}

\item
接下来添加M88kTargetCodeGenInfo类的类构造函数，从原始的TargetCodeGenInfo扩展而来。之后，必须关闭新创建的匿名命名空间:

\begin{cpp}
class M88kTargetCodeGenInfo final : public TargetCodeGenInfo {
public:
    explicit M88kTargetCodeGenInfo(CodeGen::CodeGenTypes &CGT)
        : TargetCodeGenInfo(std::make_unique<DefaultABIInfo>(CGT))
{} };
}
\end{cpp}

\item
最后，必须添加实现来创建实际的M88kTargetCodeGenInfo类std::unique\_ptr，其接受一个生成LLVM IR代码的CodeGenModule。这直接对应于最初添加到TargetInfo.h中的内容:

\begin{cpp}
std::unique_ptr<TargetCodeGenInfo>
CodeGen::createM88kTargetCodeGenInfo(CodeGenModule &CGM) {
    return std::make_unique<M88kTargetCodeGenInfo>(CGM.getTypes());
}
\end{cpp}

\end{enumerate}

以上就是ABI对前端M88k的支持。

\mySubsubsection{13.2.3.}{clang中实现对M88k工具链的支持}

clang中M88k目标集成的最后一部分将是，为我们的目标实现工具链支持。和前面一样，需要为工具链支持创建一个头文件，称这个头文件为clang/lib/Driver/ToolChains/Arch/M88k.h:

\begin{enumerate}
\item
首先，必须定义LLVM\_CLANG\_LIB\_DRIVER\_TOOLCHAINS\_ARCH\_M88K\_H，以防止以后的多次包含，并添加任何必要的头文件供以后使用。必须声明clang、driver、tools和m88k命名空间，每个命名空间都嵌套在另一个命名空间内:

\begin{cpp}
#ifndef LLVM_CLANG_LIB_DRIVER_TOOLCHAINS_ARCH_M88K_H
#define LLVM_CLANG_LIB_DRIVER_TOOLCHAINS_ARCH_M88K_H
#include "clang/Driver/Driver.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Option/Option.h"
#include <string>
#include <vector>
namespace clang {
namespace driver {
namespace tools {
namespace m88k {
\end{cpp}

\item
接下来，必须声明一个描述浮点ABI的枚举值，用于软浮点和硬浮点。浮点计算既可以由浮点硬件本身完成，很快；也可以通过软件模拟完成，很慢:

\begin{cpp}
enum class FloatABI { Invalid, Soft, Hard, };
\end{cpp}

\item
在此之后，必须添加定义，以便通过驱动程序获得浮点ABI，并通过clang的-mcpu=和-mtune=选项获得CPU。还必须声明一个从驱动程序中检索目标特性的函数:

\begin{cpp}
FloatABI getM88kFloatABI(const Driver &D, const
llvm::opt::ArgList &Args);
StringRef getM88kTargetCPU(const llvm::opt::ArgList &Args);
StringRef getM88kTuneCPU(const llvm::opt::ArgList &Args);
void getM88kTargetFeatures(const Driver &D, const
llvm::Triple &Triple, const llvm::opt::ArgList &Args,
std::vector<llvm::StringRef> &Features);
\end{cpp}

\item
最后，通过结束最初定义的名称空间和宏来结束头文件:

\begin{cpp}
} // end namespace m88k
} // end namespace tools
} // end namespace driver
} // end namespace clang
#endif // LLVM_CLANG_LIB_DRIVER_TOOLCHAINS_ARCH_M88K_H
\end{cpp}
\end{enumerate}

将实现的最后一个文件是工具链支持的C++实现，位于clang/lib/Driver/ToolChains/Arch/M88k.cpp中:

\begin{enumerate}
\item
同样，将通过包含稍后将使用的必要的头文件和命名空间来开始实现，还必须包括之前创建的M88k.h头文件:

\begin{cpp}
#include "M88k.h"
#include "ToolChains/CommonArgs.h"
#include "clang/Driver/Driver.h"
#include "clang/Driver/DriverDiagnostic.h"
#include "clang/Driver/Options.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Support/Host.h"
#include "llvm/Support/Regex.h"
#include <sstream>
using namespace clang::driver;
using namespace clang::driver::tools;
using namespace clang;
using namespace llvm::opt;
\end{cpp}

\item
接下来实现normalizeCPU()函数，该函数将CPU名称处理为clang中的-mcpu=选项，每个CPU名称都有几个可接受的变体。此外，当用户指定-mcpu=native时，可以针对当前主机的CPU类型进行编译:

\begin{cpp}
static StringRef normalizeCPU(StringRef CPUName) {
    if (CPUName == "native") {
        StringRef CPU = std::string(llvm::sys::getHostCPUName());
        if (!CPU.empty() && CPU != "generic")
        return CPU;
    }
    return llvm::StringSwitch<StringRef>(CPUName)
    .Cases("mc88000", "m88000", "88000", "generic", "mc88000")
    .Cases("mc88100", "m88100", "88100", "mc88100")
    .Cases("mc88110", "m88110", "88110", "mc88110")
    .Default(CPUName);
}
\end{cpp}

\item
接下来，必须实现getM88kTargetCPU()函数。该函数中，给定之前在clang/include/clang/Driver/Options.td文件中实现的clang CPU名称。可以得到相应的LLVM名称的M88k CPU，就是我们的目标:

\begin{cpp}
StringRef m88k::getM88kTargetCPU(const ArgList &Args) {
    Arg *A = Args.getLastArg(options::OPT_m88000, options::OPT_
        m88100, options::OPT_m88110, options::OPT_mcpu_EQ);
    if (!A)
        return StringRef();
    switch (A->getOption().getID()) {
    case options::OPT_m88000:
        return "mc88000";
    case options::OPT_m88100:
        return "mc88100";
    case options::OPT_m88110:
        return "mc88110";
    case options::OPT_mcpu_EQ:
        return normalizeCPU(A->getValue());
    default:
        llvm_unreachable("Impossible option ID");
    }
}
\end{cpp}

\item
之后实现getM88kTuneCPU()函数。这是clang选项-mtune=的行为，更改指令调度模型，以使用来自M88k的给定CPU的数据。我们只需针对当前目标CPU进行调优:

\begin{cpp}
StringRef m88k::getM88kTuneCPU(const ArgList &Args) {
    if (const Arg *A = Args.getLastArg(options::OPT_mtune_EQ))
        return normalizeCPU(A->getValue());
    return StringRef();
}
\end{cpp}

\item
还将实现getM88kFloatABI()方法，该方法获取浮点ABI。最初，将ABI设置为m88k::FloatABI::Invalid作为默认值。接下来，必须检查是否有-msoft-float或-mhard-float选项在命令行中设置。若指定了-msoft-float，则ABI设置为m88k::FloatABI::Soft。同样，当-mhard-float指定为clang时，设置m88k::FloatABI::Hard。最后，若没有指定这些选项，则选择当前平台上的默认值，即M88k的硬浮点值:

\begin{cpp}
m88k::FloatABI m88k::getM88kFloatABI(const Driver &D, const
ArgList &Args) {
    m88k::FloatABI ABI = m88k::FloatABI::Invalid;

    if (Arg *A =
        Args.getLastArg(options::OPT_msoft_float,
    options::OPT_mhard_float)) {
        if (A->getOption().matches(options::OPT_msoft_float))
            ABI = m88k::FloatABI::Soft;
        else if (A->getOption().matches(options::OPT_mhard_float))
            ABI = m88k::FloatABI::Hard;
    }
    if (ABI == m88k::FloatABI::Invalid)
        ABI = m88k::FloatABI::Hard;
    return ABI;
}
\end{cpp}

\item
接下来将添加getM88kTargetFeatures()的实现，这个函数的重要部分是作为参数传递的特征向量，所处理的唯一目标特性是浮点ABI。从驱动程序和传递给它的参数中，将获得在前一步中实现的适当的浮点ABI。注意，在软浮点ABI的特征向量中也添加了-hard-float目标特征，多以M88k目前只支持硬浮点:

\begin{cpp}
void m88k::getM88kTargetFeatures(const Driver &D,
                                 const llvm::Triple &Triple,
                                 const ArgList &Args,
                                 std::vector<StringRef> &Features) {
    m88k::FloatABI FloatABI = m88k::getM88kFloatABI(D, Args);
    if (FloatABI == m88k::FloatABI::Soft)
        Features.push_back("-hard-float");
}
\end{cpp}

\end{enumerate}

\mySubsubsection{13.3.4.}{构建具有clang集成的M88k目标}

我们几乎完成了将M88k集成到clang中的实现。最后一步是将新的clang文件添加到相应的CMakeLists.txt文件中，可以用M88k目标实现构建clang项目:

\begin{enumerate}
\item
首先，将Targets/M88k.cpp添加到clang/lib/Basic/CMakeLists.txt中。

\item
接下来，在clang/lib/CodeGen/CMakeLists.txt中添加Targets/M88k.cpp。

\item
最后，将ToolChains/Arch/M88k.cpp添加到clang/lib/Driver/CMakeLists.txt中。
\end{enumerate}

好了!这就是对M88k目标的工具链支持的工具链实现，我们已经完成了对M88k的clang的集成!

需要做的最后一步是用M88k目标构建clang。下面的命令将构建clang和LLVM项目。对于clang，要注意M88k目标。必须设置CMake选项-DLLVM\_EXPERIMENTAL\_TARGETS\_TO\_BUILD=M88k:

\begin{shell}
$ cmake -G Ninja ../llvm-project/llvm -DLLVM_EXPERIMENTAL_
TARGETS_TO_BUILD=M88k -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_
PROJECTS="clang;llvm"
$ ninja
\end{shell}

现在应该有一个可以识别M88k目标的clang版本了!可以通过-print-targets选项检查clang支持的目标列表来确认这一点:

\begin{shell}
$ clang --print-targets | grep M88k
    m88k        - M88k
\end{shell}

本节中，深入研究了将新后端目标集成到clang中并使其被识别的技术细节。下一节中，我们将探讨交叉编译的概念，详细介绍针对不同于当前主机CPU架构的过程。










